# -*- coding: utf-8 -*-
# @Time    : 2024/6/18 15:43
# @Author  : yujiahao
# @File    : 29_encapsulation_inheritance_polymorphism.py
# @description:Python面向对象三大特性：封装、继承、多态


# todo 一.封装
'''
把很多数据封装到⼀个对象中. 把固定功能的代码封装到⼀个代码块, 函数, 对象, 打包成模块. 这都属于封装的思想. 具体的情况具体分析.
比如. 你写了⼀个很⽜B的函数. 那这个也可以被称为封装. 在⾯向对象思想中. 是把⼀些看似⽆关紧要的内容组合到⼀起统⼀进⾏存储和使⽤. 这就是封装.

封装，顾名思义就是将内容封装到某个地方，以后再去调用被封装在某处的内容。
所以，在使用面向对象的封装特性时，需要：

     - 将内容封装到某处
     - 从某处调用被封装的内容

综上所述，对于面向对象的封装来说，其实就是使用构造方法将内容封装到 对象 中，然后通过对象直接或者self间接获取被封装的内容
'''


# 创建类
class Foo:
    # 构造方法，在根据类创建对象时自动执行
    def __init__(self, name, age):
        # 将传入的参数 name 封装到实例的 name 属性中
        self.name = name
        # 将传入的参数 age 封装到实例的 age 属性中
        self.age = age

    # 通过self间接调用被封装的内容
    def detail(self):
        print(self.name)
        print(self.age)


# 根据类 Foo 创建对象 obj1
# 自动执行 Foo 类的 __init__ 方法
# 将 'wupeiqi' 和 18 分别封装到 obj1 的 name 和 age 属性中
obj1 = Foo('wupeiqi', 18)

print(obj1.name)  # 直接调用obj1对象的name属性
print(obj1.age)  # 直接调用obj1对象的age属性

# 根据类 Foo 创建对象 obj2
# 自动执行 Foo 类的 __init__ 方法
# 将 'alex' 和 73 分别封装到 obj2 的 name 和 age 属性中
obj2 = Foo('alex', 73)

print(obj2.name)  # 直接调用obj2对象的name属性
print(obj2.age)  # 直接调用obj2对象的age属性

# todo 第一步：将内容封装到某处

'''
self 是一个形式参数，当执行 obj1 = Foo('wupeiqi', 18 ) 时，self 等于 obj1
当执行 obj2 = Foo('alex', 78 ) 时，self 等于 obj2
所以，内容其实被封装到了对象 obj1 和 obj2 中，每个对象中都有 name 和 age 属性，在内存里类似于下图来保存。

        对象属性说明：
        obj1:
          name = wupeiqi
          age = 18
        obj2:
          name = alex
          age = 73
'''

# todo 第二步：从某处调用被封装的内容

'''
调用被封装的内容时，有两种情况：

    - 通过对象直接调用
    - 通过self间接调用
1、通过对象直接调用被封装的内容

上图展示了对象 obj1 和 obj2 在内存中保存的方式，根据保存格式可以如此调用被封装的内容：对象.属性名

    obj1 = Foo('wupeiqi', 18)
    print obj1.name    # 直接调用obj1对象的name属性
    print obj1.age     # 直接调用obj1对象的age属性
    
    obj2 = Foo('alex', 73)
    print obj2.name    # 直接调用obj2对象的name属性
    print obj2.age     # 直接调用obj2对象的age属性

2、通过self间接调用被封装的内容

执行类中的方法时，需要通过self间接调用被封装的内容

obj1 = Foo('wupeiqi', 18)
obj1.detail()  # Python默认会将obj1传给self参数，即：obj1.detail(obj1)，所以，此时方法内部的 self ＝ obj1，即：self.name 是 wupeiqi ；self.age 是 18

obj2 = Foo('alex', 73)
obj2.detail()  # Python默认会将obj2传给self参数，即：obj1.detail(obj2)，所以，此时方法内部的 self ＝ obj2，即：self.name 是 alex ； self.age 是 78


'''

# todo  二.继承
'''
【这里不细说了，看28_class_extend.py】
    ⼦类可以⾃动拥有⽗类中除了私有属性外的其他所有内容. 说⽩了, ⼉⼦可以随便⽤爹的东⻄. 但是朋友们, ⼀定要认清楚⼀个事情. 
必须先有爹, 后有⼉⼦. 顺序不能乱, 在python中实现继承非常简单. 在声明类的时候, 在类名后⾯添加⼀个⼩括号,就可以完成继承关系. 
那么什么情况可以使⽤继承呢? 单纯的从代码层⾯上来看. 两个类具有相同的功能或者特征的时候. 可以采⽤继承的形式. 
提取⼀个⽗类, 这个⽗类中编写着两个类相同的部分. 然后两个类分别取继承这个类就可以了. 这样写的好处是我们可以避免写很多重复的功能和代码. 
如果从语义中去分析的话. 会简单很多. 如果语境中出现了x是⼀种y. 这时, y是⼀种泛化的概念. x比y更加具体. 那这时x就是y的⼦类. 
比如. 猫是⼀种动物. 猫继承动物. 动物能动. 猫也能动. 这时猫在创建的时候就有了动物的"动"这个属性. 再比如, ⽩骨精是⼀个妖怪. 
妖怪天⽣就有⼀个比较不好的功能叫"吃⼈", ⽩骨精⼀出⽣就知道如何"吃⼈". 此时 ⽩骨精继承妖精.

'''

# todo  三.多态

"""

同⼀个对象, 多种形态. 这个在python中其实是很不容易说明⽩的. 因为我们⼀直在⽤. 只是没有具体的说. 比如. 我们创建⼀个变量a = 10 , 我们知道此时a是整数类型. 
但是我们可以通过程序让a = "alex", 这时, a⼜变成了字符串类型. 这是我们都知道的. 但是, 我要告诉你的是. 这个就是多态性. 同⼀个变量a可以是多种形态。

多态，同一个对象，多种形态。python默认支持多态。



我这里提供一下Java的多态理解一下：就只是理解一下多态的概念

    ① 本态
        子类对象的子类形态----只有一种情况本态：要大量的频繁的使用子类特有的成员；
    
    ② 多态--本质、表现及应用
        概念：子类对象的多种父类形态，把子类对象当父类对象来使用;
        
        1) 本质：把子类对象“看做是”“当做”父类对象来使用，只是切换了一个视角，对象的本体永不变；
           # Person p = new Chinese();
           
        2) 多态的两方面：
            1> 对象的多态性  从右向左，子类对象的多种父类形态；
            2> 引用变量的多态性  从左向右，父类类型的引用变量指向多个不同子类对象
        
        3) 如果左边编译时和运行时类型不一致，就产生了多态
        
        4) 多态应用：多态数组具有兼容性，只要是本类或者子类对象都可以保存在其中--非常典型的应用；
        
        5) 多态最大的好处：把不同的对象批量处理；
        
        6) 多态参数方法：可以兼容所有本类和子类对象，但是带了类型的模糊不确定。
    
    ③ 多态的副作用
        1> 多态引用时，子类特有成员无法访问了。
        2> 造型 （classCast），没有强制意味；但是有风险，不清楚对方是谁，强制转很危险，
           # 参考强转符--(（Chinese)p).Spring
        3> 造型有风险，必须先判断,所有的都要判断
    
    ④ 多态副作用根本原因：p1指向不明确，
        1> 编译器角度看：P1是Person类型的对象，Person类型的事物不具备这样的行为，
        2> 若编译时类型和运行时类型不一致，就出现多态--左边类型称为编译时类型，右边类型称为运行时类型
        3> 向上转型(upcasting)子类可以看做是特殊的父类，所以父类类型的引用可以指向子类的对象
    
    ⑤ 虚拟方法调用表示
        # if ( 对方是真的是中国人 ) {}
        if p instanceof Chinese:  # 作用：判断是左边的引用指向的对象实体，是否是 右面类型的对象
            Chinese ch = (Chinese)p  # 对象的类型判断必须从子类到父类
        elif p instanceof American:
            American am = (American)
        else:
            pass
    
    ⑥ 多态引用调用重写方法
        虚拟方法调用（是多态最内核的东西）：p1.say()--执行子类的
        1) 编译时要检查父类类型
        2) 运行时要执行子类类型（执行时类型），称为动态绑定--取决于new的是谁，但是编译还是通过父类，
           唯一作用就是骗过编译器，所以很虚，
    
    ⑦ 虚拟方法内在逻辑
        此行为在父类类型中应该具有的，但是怎么具体行为不知道，交给子类去重写。
        1> 子类中的方法可以覆盖父类中的方法---所以方法具有多态性；
        2> 子类中的属性和父类的属性是共存的关系，没有多态性
    
    ⑧ 多态数组、可变参数
        多态数组, 只要是本类或子类对象都可以保存在其中
        多态参数方法, 可以兼容所有本类和子类对象, 但是带了类型的模糊不确定性.
        @param p Person类型及其子类类型的对象
    
    ⑨ instanceof 操作符
        x instanceof A：检验x是否为类A的对象，返回值为boolean型。
        要求x所属的类与类A必须是子类和父类的关系，否则编译错误。
        如果x属于类A的子类B，x instanceof  A值也为true。
        public class Person extends Object {…}
        public class Student extends Person {…}
        public class Graduate extends Person {…}
        -------------------------------------------------------------------
        public void method1(Person e) {
            if (e instanceof Person) 
                # 处理Person类及其子类对象
            if (e instanceof Student) 
                # 处理Student类及其子类对象
            if (e instanceof Graduate)
                # 处理Graduate类及其子类对象
    
    ⑩ 对象类型转换 (Casting)
        ① 基本数据类型的Casting：
            自动类型转换：小的数据类型可以自动转换成大的数据类型
                     如long g=20;           double d=12.0f
            强制类型转换：可以把大的数据类型强制转换(casting)成小的数据类型
                     如 float f=(float)12.0;   int a=(int)1200L
        ② 对Java对象的强制类型转换称为造型
            从子类到父类的类型可以自动进行
            从父类到子类的类型转换必须通过造型(强制类型转换)实现
            无继承关系的引用类型间的转换是非法的
            在造型前可以使用instanceof操作符测试一个对象的类型
    
    对象类型转换举例
    public class Test{ 
        public static void method(Person e) {    
            # 设Person类中没有getSchool()方法
            # System.out.pritnln(e.getSchool());   # 非法,编译时错误
    
            if e instanceof Student:
                Student me = (Student)e  # 将e造型为Student类型
                System.out.pritnln(me.getschool())
        
        public static void main(Stirng args[]){
            Test t = new Test()
            Student m = new Student()
            t.method(m)
    }
"""

# 在java或者c#定义变量或者给函数传值必须定义数据类型，否则就报错。

'''def func(int a):
    print('a必须是数字')
'''


# 而类似于python这种弱定义类语言,a可以是任意形态（str,int,object等等）。
def func(a):
    print('a是什么都可以')


# 再比如：
class F1:
    pass


class S1(F1):

    def show(self):
        print('S1.show')


class S2(F1):

    def show(self):
        print('S2.show')


# 由于在Java或C#中定义函数参数时，必须指定参数的类型
# 为了让Func函数既可以执行S1对象的show方法，又可以执行S2对象的show方法，所以，定义了一个S1和S2类的父类
# 而实际传入的参数是：S1对象和S2对象

'''

def Func(F1 obj):
"""Func函数需要接收一个F1类型或者F1子类的类型"""

    print obj.show()


s1_obj = S1()
Func(s1_obj)  # 在Func函数中传入S1类的对象 s1_obj，执行 S1 的show方法，结果：S1.show

s2_obj = S2()
Func(s2_obj)  # 在Func函数中传入Ss类的对象 ss_obj，执行 Ss 的show方法，结果：S2.show


'''


# 鸭子类型

# python中有一句谚语说的好，你看起来像鸭子，那么你就是鸭子。
# 对于代码上的解释其实很简答：
class A:
    def f1(self):
        print('in A f1')

    def f2(self):
        print('in A f2')


class B:
    def f1(self):
        print('in A f1')

    def f2(self):
        print('in A f2')


obj = A()
obj.f1()
obj.f2()

obj2 = B()
obj2.f1()
obj2.f2()
# A 和 B两个类完全没有耦合性，但是在某种意义上他们却统一了一个标准。
# 对相同的功能设定了相同的名字，这样方便开发，这两个方法就可以互成为鸭子类型。

# 这样的例子比比皆是：str  tuple list 都有 index方法，这就是统一了规范。
# str bytes 等等 这就是互称为鸭子类型。


print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')


# todo 四.类的约束


# 看个例子

# 公司让小明给他们的网站完善一个支付功能，小明写了两个类，如下：

class QQpay:
    def pay(self, money):
        print('使用qq支付%s元' % money)


class Alipay:
    def pay(self, money):
        print('使用阿里支付%s元' % money)


a = Alipay()
a.pay(100)

b = QQpay()
b.pay(200)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')


# 但是上面这样写不太放方便，也不合理，老大说让他整改，统一一下付款的方式，小明开始加班整理：

class QQpay:
    def pay(self, money):
        print('使用qq支付%s元' % money)


class Alipay:
    def pay(self, money):
        print('使用阿里支付%s元' % money)


def pay(obj, money):  # 这个函数就是统一支付规则，这个叫做： 归一化设计。
    obj.pay(money)


a = Alipay()
b = QQpay()

pay(a, 100)
pay(b, 200)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')


# 写了半年的接口，小明终于接了大项目了，结果公司没品位，招了一个野生的程序员春哥接替小明的工作，老大给春哥安排了任务，让他写一个微信支付的功能：

class QQpay:
    def pay(self, money):
        print('使用qq支付%s元' % money)


class Alipay:
    def pay(self, money):
        print('使用阿里支付%s元' % money)


class Wechatpay:  # 野生程序员一般不会看别人怎么写，自己才是最好，结果......
    def fuqian(self, money):
        print('使用微信支付%s元' % money)


def pay(obj, money):
    obj.pay(money)


a = Alipay()
b = QQpay()

pay(a, 100)
pay(b, 200)

c = Wechatpay()
c.fuqian(300)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')


# 结果春哥，受惩罚了，限期整改，那么春哥，发奋图强，python的相关资料，重新梳理的代码：

class Payment:

    # 此类什么都不做，就是制定一个标准，谁继承我，必须定义我里面的方法。

    def pay(self, money): pass


class QQpay(Payment):
    def pay(self, money):
        print('使用qq支付%s元' % money)


class Alipay(Payment):
    def pay(self, money):
        print('使用阿里支付%s元' % money)


class Wechatpay(Payment):
    def fuqian(self, money):
        print('使用微信支付%s元' % money)


def pay(obj, money):
    obj.pay(money)


a = Alipay()
b = QQpay()

pay(a, 100)
pay(b, 200)

c = Wechatpay()
c.fuqian(300)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

'''
但是，这样还会有问题，如果再来野生程序员，他不看其他的支付方式，也不知道为什么继承的类中要定义一个没有意义的方法，所以他会是会我行我素

所以此时我们要用到对类的约束，对类的约束有两种：

    1.提取⽗类. 然后在⽗类中定义好⽅法. 在这个⽅法中什么都不⽤⼲. 就抛⼀个异常就可以了. 这样所有的⼦类都必须重写这个⽅法. 否则. 访问的时候就会报错.
    
    2.使⽤元类来描述⽗类. 在元类中给出⼀个抽象⽅法. 这样⼦类就不得不给出抽象⽅法的具体实现. 也可以起到约束的效果.
'''


# 先用第一种方式解决：Python中推荐

class Payment:
    """
    此类什么都不做，就是制定一个标准，谁继承我，必须定义我里面的方法。
    """

    def pay(self, money):
        raise Exception("你没有实现pay方法")


class QQpay(Payment):
    def pay(self, money):
        print('使用qq支付%s元' % money)


class Alipay(Payment):
    def pay(self, money):
        print('使用阿里支付%s元' % money)


class Wechatpay(Payment):
    def pay(self, money):
        print("我如果不实现，会抛异常的")

    def fuqian(self, money):
        print('使用微信支付%s元' % money)


def pay(obj, money):
    obj.pay(money)


a = Alipay()
b = QQpay()
c = Wechatpay()
pay(a, 100)
pay(b, 200)
pay(c, 300)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# 第二种方式：引入抽象类的概念处理 【用Java的使用这个，你可以自己定义一个抽象类，含有抽象方法，也可以自定义一个接口，都是为了统一规范，很方便的】

'''

总结: 约束. 其实就是⽗类对⼦类进⾏约束. ⼦类必须要写xxx⽅法. 在python中约束的⽅式和⽅法有两种:

    1. 使⽤抽象类和抽象⽅法, 由于该⽅案来源是java和c#. 所以使⽤频率还是很少的
    
    2. 使⽤⼈为抛出异常的⽅案. 并且尽量抛出的是NotImplementError. 这样比较专业, ⽽且错误比较明确.(推荐)
'''

from abc import ABCMeta, abstractmethod


class Payment(metaclass=ABCMeta):  # 抽象类 接口类  规范和约束  metaclass指定的是一个元类
    @abstractmethod
    def pay(self): pass  # 抽象方法


class Alipay(Payment):
    def pay(self, money):
        print('使用支付宝支付了%s元' % money)


class QQpay(Payment):
    def pay(self, money):
        print('使用qq支付了%s元' % money)


class Wechatpay(Payment):
    def pay(self, money):
        print("我如果不实现，会抛异常的")

    def recharge(self): pass


def pay(a, money):
    a.pay(money)


a = Alipay()
a.pay(100)
pay(a, 100)  # 归一化设计：不管是哪一个类的对象，都调用同一个函数去完成相似的功能
q = QQpay()
q.pay(100)
pay(q, 100)
w = Wechatpay()
pay(w, 100)  # 到用的时候才会报错

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 五.super

'''

super() 是 Python 中一个非常重要且有用的内置函数，特别是在类继承和多重继承的情况下。 super是严格按照类的继承顺序执行。

super() 的基本概念
super() 函数返回一个代理对象，该对象会将方法调用委托给类的父类或兄弟类。它主要用于解决以下问题：

    - 调用父类的方法：可以在子类中调用父类的方法，而无需显式地引用父类的名称。
    - 避免重复代码：在子类中可以重用父类的方法实现，减少代码重复。
    - 支持多重继承：在多重继承的情况下，super() 可以按照方法解析顺序（MRO）调用合适的父类方法。

'''


class A:
    def f1(self):
        print('in A f1')

    def f2(self):
        print('in A f2')


class Foo(A):
    def f1(self):
        super().f2()
        print('in A Foo')


obj = Foo()
obj.f1()

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# super()严格按照类的mro顺序执行
'''
结果：
in Bar
in Foo
in Info f1
分析：先使用C3线性算法，列出mro的顺序，接着按照顺序调用就明白了

    mro(Info) = mro(Info(Foo, Bar))
    原式 = [Info] + merge(mro(Foo), mro(Bar), [Foo, Bar])
    
    计算 mro(Foo):
    mro(Foo) = mro(Foo(A))
             = [Foo] + merge(mro(A), [A])
             = [Foo] + merge([A, object], [A])
             = [Foo, A] + merge([object])
             = [Foo, A, object]
    
    计算 mro(Bar):
    mro(Bar) = mro(Bar(A))
             = [Bar] + merge(mro(A), [A])
             = [Bar] + merge([A, object], [A])
             = [Bar, A] + merge([object])
             = [Bar, A, object]
    
    合并 mro(Foo) 和 mro(Bar):
    原式 = [Info] + merge([Foo, A, object], [Bar, A, object], [Foo, Bar])
         = [Info, Foo] + merge([A, object], [Bar, A, object], [Bar])
         = [Info, Foo, Bar] + merge([A, object], [A, object])
         = [Info, Foo, Bar, A] + merge([object], [object])
         = [Info, Foo, Bar, A, object]
    
    最终结果：
    mro(Info) = [Info, Foo, Bar, A, object]

所以调用f1的方法就按照这个mro的顺序调用就行，想明白这个就都清楚了，最好手写，完成之后可以调用mro()方法打印一下看看。
'''


class A:
    def f1(self):
        print('in A')


class Foo(A):
    def f1(self):
        super().f1()
        print('in Foo')


class Bar(A):
    def f1(self):
        print('in Bar')


class Info(Foo, Bar):
    def f1(self):
        super().f1()
        print('in Info f1')


obj = Info()
obj.f1()

print(Info.mro())
